嗨 大家好 我是一路爬坡的阿肥
最近天氣開始有點涼爽,騎車去上班瀏海也不分岔了~
上班族小確幸++ 
今日文章適合搭配範例專案的packages/day06-creational-factory-method.code一起觀看,歡迎把專案clone下來喔
阿肥的範例專案以Mono-Repo的方式管理,在packages底下的所有目錄都會視為獨立的專案,所以我們可以看到會有package.json設定相關資訊,source code的部分主要會放在src的目錄中。
以今天的例子來說,基本上會有這些檔案:
declaration.ts: 類別、屬性、資料結構等相關的定義factory.ts: 設計模式實作的類別index.tsx:React元件的主程式Vermicelli.story.tsx:執行Storybook後,呈現UI flow 的story檔。Storybook是最近幾年相當熱門的前端開發工具,對於元件的手動測試幫助相當大,阿肥之後會再抽出一天跟大家介紹喔。Vermicelli.test.tsx:元件的測試檔,本專案用Facebook開發的Jest作為測試框架,之後也會進行分享。乍聽之下覺得是廢話,不過其實是有意義的!就像小時候玩黏土一樣,你會先知道要先做手、腳、頭、身軀,最後再接起來形成人偶。我們要先定義麵線的口味和顏色、客人填的菜單有哪些選項,以及最後給客人的產品。
在接下來的範例中,阿肥會把這些定義包在一個 namespace 中,只要其他檔案 reference 到這個檔案,就可以參考到裡面的定義。
namespace Declaration {
    // 麵線顏色的種類
    export type T_Color = "white" | "red";
    // 麵線的口味種類,目前有大腸跟蚵仔兩種選項
    export type T_Flavor = "intestine" | "oyster";
    // 客人的菜單內容
    export interface I_Order {
        flavor: T_Flavor;
        color: T_Color;
        spoons: number;
    }
    // 最後給客人的麵線組成,繼承了I_Order
    export interface I_Vermicelli extends I_Order {
        trayed: boolean;
        content: string[];
    }
}
首先,我們先把定義檔參考進來:/// <reference path="declaration.ts" />
接著,我們實作一個抽象類別BaseVermicelli來為流程做出一個介面,讓做白麵線的WhiteVermicelli跟RedVermicelli可以繼承他實作各自的流程。
最後,我們實作類別FrontStaff與VermicelliFactory。因為主程式中,我們只會用到 VermicelliFactory來取得麵線,其他的類別與邏輯都會封裝在裡面,所以我們只要匯出這個即可。
// 用 abstract 的關鍵字宣告這個是抽象類別
abstract class BaseVermicelli {
    //...
}
//繼承 BaseVermicelli
class WhiteVermicelli extends BaseVermicelli {
    //...
}
//繼承 BaseVermicelli
class WhiteVermicelli extends BaseVermicelli {
    //...
}
class FrontStaff {
    //...
}
// 用 export 匯出 VermicelliFactory 給外部使用
export class VermicelliFactory {
    //...
}
跟factory.ts一樣,我們先把定義先參考進來:/// <reference path="declaration.ts" />
接著是React起手式,先宣告一個元件,先簡單return一個div。export const FatVermicelli: React.FC => <div></div>
阿肥這邊把FatVermicelli用React.FC定義為這是個 Functional Component (FC)。簡單來說,不同於過去繼承React元件類別的方式,而是用最單純的方式 - function 建立元件,不僅可以更方便為元件的邏輯進行測試,在React 16.8 之後正式推出的 React Hooks,更是讓FC可以實現state的管理、life cycle的事件處理等等。所以接下來的範例,阿肥都會盡量用FC實作,讓大家順便熟悉喔。
客人菜單的部分我們用表單元件實作,然後用React.useState管理各欄位值的變化,就像這樣:
// Declaration.I_Order 定義 order state,並給定預設值
const [order, setOrder] = React.useState<Declaration.I_Order>({
    color: "red",
    flavor: "intestine",
    spoons: 1
});
// 用 setOrder 更新欄位值
const handleFieldChange = ({ target }) => {
    setOrder(order => ({
      ...order,
      [target.name]: target.value
    }));
  };
// value傳入 order.flavor 控制欄位值,並在 onChange 事件傳入 handleFieldChange 執行 setOrder
<select
    value={order.flavor}
    id="flavor"
    name="flavor"
    onChange={handleFieldChange}
>
    <option value="intestine">大腸</option>
    <option value="oyster">蚵仔</option>
</select>
當送出表單後,我們用表單的 onSubmit 事件來觸發製作麵線的方法,並且定義props裡有個 onSubmit,將做好的麵線傳給外部。
interface I_Props_FatVermicelli {
  // 當麵線完成後,再交給外面的人
  onSubmit: (v: Declaration.I_Vermicelli) => void;
}
// 宣告FC的 props 型別為 I_Props_FatVermicelli
export const FatVermicelli: React.FC<I_Props_FatVermicelli> = ({
  onSubmit
}) => {
    // 建立一個麵線工廠的實體
    const vermicelliFactory = new VermicelliFactory();
    // 執行製作麵線的流程,並回傳給 props 的 onSubmit
    const handleSubmitOrder = e => {
        e.preventDefault();
        vermicelliFactory.receiveOrder(order);
        let _vermicelli = vermicelliFactory.maker.vermicelli;
        if (onSubmit) onSubmit(_vermicelli);
    };
    //...
    return (
        // 在 onChange 事件傳入 handleSubmitOrder 執行
        <form onSubmit={handleSubmitOrder}>
    )
}
執行yarn story後,開啟http://localhost:6006,然後切到FatVermicelli,就可以看到畫面了。
從昨天工廠方法的模式篇,到今天的實作篇,大致是之後介紹每種模式的步驟:先讓大家從生活化的情境了解設計模式的理論,再藉由實作來加深對設計模式的理解。如果有興趣的話,可以訂閱阿肥這系列的文章,這樣阿肥會更有動力完賽喔!